﻿
//#define USE_ANGLE

using OpenTK;
using OpenTK.Graphics;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms.Integration;
using System.Windows.Media;
using SharpDepend;
using SharpDepend.Datatypes;
using ZquenceStudio3_SharedClasses;
using System.Windows.Forms;
using System.Security;
using System.Runtime.InteropServices;
using System.Windows.Input;

#if USE_ANGLE
using OpenTK.Graphics.ES20;
#endif

namespace SharpDepend.WindowsDesktop.Renderer
{
    /// <summary>
    /// Reference: http://neokabuto.blogspot.se/2013/07/opentk-tutorial-3-enter-third-dimension.html
    /// </summary>
    class OpenGLRenderer : WindowsFormsHost, IGraphicsDevice
    {
        #region Shit

        public event Action RaiseOnRender;
        private OpenGLRendererShared gl;
        private List<Texture> mLoadedTextures = new List<Texture>();
        private bool mHasInit;
        private List<Texture> mTextures;
        private System.Collections.ObjectModel.ReadOnlyCollection<Texture> mTexturesReadonly;

        GLControl glControl;
        Rect bounds;

        public System.Collections.ObjectModel.ReadOnlyCollection<Texture> Textures
        {
            get { return mTexturesReadonly; }
        }
        
        public bool Ready
        {
            get;
            private set;
        }

        public OpenGLRenderer()
        {
            mTextures = new List<Texture>();
            mTexturesReadonly = new System.Collections.ObjectModel.ReadOnlyCollection<Texture>(mTextures);

            this.Loaded += OpenGLRenderer_Loaded;
            this.SizeChanged += OpenGLRenderer_SizeChanged;
        }

        void OpenGLRenderer_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            SetViewportSize(e.NewSize.Width, e.NewSize.Height);
        }

        private void GlControl_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
        {

        }

        private void GlControl_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
        {
            System.Windows.Input.Key wpfButton = ConvertToWpf(e.KeyCode);

            System.Windows.Input.KeyEventArgs args = new System.Windows.Input.KeyEventArgs(Keyboard.PrimaryDevice, PresentationSource.FromVisual(this), 0, wpfButton);
            args.RoutedEvent = Keyboard.KeyDownEvent;
            args.Source = this;
            RaiseToParent(args);
        }

        void glControl_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            System.Windows.Input.MouseButton? wpfButton = ConvertToWpf(e.Button);
            if (!wpfButton.HasValue)
                return;
            
            System.Windows.Input.MouseButtonEventArgs args = new System.Windows.Input.MouseButtonEventArgs(System.Windows.Input.Mouse.PrimaryDevice, 0, wpfButton.Value);
            args.RoutedEvent = System.Windows.Input.Mouse.PreviewMouseDownEvent;
            args.Source = this;
            RaiseToParent(args);
        }

        internal static UIElement MouseOverElement;

        private void GlControl_MouseHover(object sender, EventArgs e)
        {
            MouseOverElement = this;
            
            System.Windows.Input.MouseEventArgs args = new System.Windows.Input.MouseEventArgs(System.Windows.Input.Mouse.PrimaryDevice, 0);
            args.RoutedEvent = System.Windows.Input.Mouse.MouseEnterEvent;
            args.Source = this;
            RaiseToParent(args);
        }

        void glControl_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            MouseOverElement = this;

            System.Windows.Input.MouseButton? wpfButton = ConvertToWpf(e.Button);
            if (!wpfButton.HasValue)
            {
                GlControl_MouseHover(sender, e);
                return;
            }

            System.Windows.Input.MouseButtonEventArgs args = new System.Windows.Input.MouseButtonEventArgs(System.Windows.Input.Mouse.PrimaryDevice, 0, wpfButton.Value);
            args.RoutedEvent = System.Windows.Input.Mouse.PreviewMouseMoveEvent;
            args.Source = this;
            RaiseToParent(args);
        }

        void glControl_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            System.Windows.Input.MouseButton? wpfButton = ConvertToWpf(e.Button);
            if (!wpfButton.HasValue)
                return;

            System.Windows.Input.MouseButtonEventArgs args = new System.Windows.Input.MouseButtonEventArgs(System.Windows.Input.Mouse.PrimaryDevice, 0, wpfButton.Value);
            args.RoutedEvent = System.Windows.Input.Mouse.PreviewMouseUpEvent;
            args.Source = this;
            RaiseToParent(args);
        }
        
        void GlControl_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            System.Windows.Input.MouseWheelEventArgs args = new System.Windows.Input.MouseWheelEventArgs(System.Windows.Input.Mouse.PrimaryDevice, 0, e.Delta);
            args.RoutedEvent = System.Windows.Input.Mouse.PreviewMouseWheelEvent;
            args.Source = this;
            RaiseToParent(args);
        }

        [System.Diagnostics.DebuggerStepThrough]
        void RaiseToParent(RoutedEventArgs args)
        {
            try
            {
                // This will fail sometimes, when user change window size or goes into a beakpoint and
                // continue. Juste skip this exception and continue.
                FrameworkElement parent1 = Parent as FrameworkElement;
                parent1.RaiseEvent(args);
            }
            catch (Exception)
            {
            }
        }
        
        public static System.Windows.Input.Key ConvertToWpf(System.Windows.Forms.Keys inputKey)
        {
            switch (inputKey)
            {
                case Keys.Control:
                case Keys.ControlKey:
                case Keys.LControlKey: return System.Windows.Input.Key.LeftCtrl;
                case Keys.RControlKey: return System.Windows.Input.Key.RightCtrl;
            }
            if (inputKey == Keys.ControlKey)
            {
                return System.Windows.Input.Key.LeftCtrl;
            }
            else if (inputKey == Keys.Menu)
            {
                return System.Windows.Input.Key.LeftAlt;
            }
            else if (inputKey == Keys.ShiftKey)
            {
                return System.Windows.Input.Key.LeftShift;
            }


            try
            {
                return (System.Windows.Input.Key)Enum.Parse(typeof(System.Windows.Input.Key), inputKey.ToString());
            }
            catch
            {
                // There wasn't a direct mapping...     
                return System.Windows.Input.Key.None;
            }
        }

        private System.Windows.Input.MouseButton? ConvertToWpf(System.Windows.Forms.MouseButtons winformButton)
        {
            switch (winformButton)
            {
                case System.Windows.Forms.MouseButtons.Left:
                    return System.Windows.Input.MouseButton.Left;
                case System.Windows.Forms.MouseButtons.None:
                    return null;
                case System.Windows.Forms.MouseButtons.Right:
                    return System.Windows.Input.MouseButton.Right;
                case System.Windows.Forms.MouseButtons.Middle:
                    return System.Windows.Input.MouseButton.Middle;
                case System.Windows.Forms.MouseButtons.XButton1:
                    return System.Windows.Input.MouseButton.XButton1;
                case System.Windows.Forms.MouseButtons.XButton2:
                    return System.Windows.Input.MouseButton.XButton2;
                default:
                    throw new ArgumentOutOfRangeException("winformButton");
            }
        }

        /// <summary>
        /// Get the platformspecific DPI scale.
        /// </summary>
        /// <returns>The DPI scale.</returns>
        double GetDPIScale()
        {
            PresentationSource presentationsource = PresentationSource.FromVisual(this);

            // Make sure it's connected.
            if (presentationsource != null)
            {
                return presentationsource.CompositionTarget.TransformToDevice.M11;
            }

            return 0;
        }

        class Apan : GLControl
        {
            public Apan(GraphicsMode mode, int major, int minor, GraphicsContextFlags flags)
                : base(mode, major, minor, flags)
            {
            }

            protected override void OnMouseWheel(System.Windows.Forms.MouseEventArgs e)
            {
                base.OnMouseWheel(e);
            }

            protected override void OnScroll(ScrollEventArgs se)
            {
                base.OnScroll(se);
            }
        }

        void OpenGLRenderer_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            if (glControl == null)
            {
                var flags = GraphicsContextFlags.Default;
#if USE_ANGLE
            flags = GraphicsContextFlags.Embedded;
#endif

                CaptureMouseWheelWhenUnfocusedBehavior.SetIsEnabled(this, true);

                WindowsFormsHost wfh = (WindowsFormsHost)sender;

                const int multisamples = 2;
                glControl = new Apan(new GraphicsMode(32, 24, 0, multisamples), 2, 0, flags);
                glControl.MakeCurrent();
                glControl.Paint += GLControl_Paint;
                glControl.Dock = System.Windows.Forms.DockStyle.Fill;
                glControl.MouseDown += glControl_MouseDown;
                glControl.MouseMove += glControl_MouseMove;
                glControl.MouseUp += glControl_MouseUp;
                glControl.MouseHover += GlControl_MouseHover;
                //glControl.Scroll += glControl_Scroll;
                glControl.MouseWheel += GlControl_MouseWheel;
                glControl.KeyDown += GlControl_KeyDown;
                glControl.KeyPress += GlControl_KeyPress;
                wfh.Child = glControl;

                var parent = this.Parent;
                double width = 0;
                double height = 0;
                if (parent != null)
                {
                    width = (this.Parent as System.Windows.Controls.Control).ActualWidth;
                    height = (this.Parent as System.Windows.Controls.Control).ActualHeight;
                }
                else
                {
                    // Parent not set, try to take this actual size.
                    width = this.ActualWidth;
                    height = this.ActualHeight;
                }

                bounds.Width = width;
                bounds.Height = height;

                gl = new OpenGLRendererShared();
                gl.InitialiseData((float)bounds.Width, (float)bounds.Height, (float)GetDPIScale());

                LoadQueuedTextures();

                mHasInit = true;

                glControl.Context.MakeCurrent(null);

                Ready = true;

            }
            else
            {
                LoadQueuedTextures();
            }
        }

        private void LoadQueuedTextures()
        {
            foreach (var texture in mLoadedTextures)
            {
                InternalAddTexture(texture);
            }
            mLoadedTextures.Clear();
        }

        #endregion

        /// <summary>
        /// Get the graphics display if available.
        /// </summary>
        public IGraphicsDisplay GraphicsDisplay { get; private set; }

        /// <summary>
        /// Calls wqhen this instance has been created.
        /// </summary>
        /// <param name="graphicsDisplay"></param>
        public void Initialize(IGraphicsDisplay graphicsDisplay)
        {
            GraphicsDisplay = graphicsDisplay;
        }

        /// <summary>
        /// Reference: https://www.opengl.org/discussion_boards/showthread.php/172280-Constructing-an-orthographic-matrix-for-2D-drawing
        /// </summary>
        public void SetViewportSize(double width, double height)
        {
            bounds.Width = width;
            bounds.Height = height;

            dpiScale = GetDPIScale();

            LazyUpdateGL = true;
        }

        bool LazyUpdateGL = false;

        void LazyChangeSize()
        {
            if (glControl != null)
            {
                glControl.MakeCurrent();

                if (gl != null)
                {
                    gl.InitOpenGL(bounds.Width, bounds.Height, dpiScale);

                    gl.UpdateViewport();
                }

                glControl.Context.MakeCurrent(null);

                LazyUpdateGL = false;
            }
        }

        private void GLControl_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
        {
            if (RaiseOnRender != null)
            {
                RaiseOnRender();
            };
        }

        private static Rect GetBoundingBox(FrameworkElement element, FrameworkElement containerWindow)
        {
            GeneralTransform transform = element.TransformToAncestor(containerWindow);
            Point topLeft = transform.Transform(new Point(0, 0));
            Point bottomRight = transform.Transform(new Point(element.ActualWidth, element.ActualHeight));
            return new Rect(topLeft, bottomRight);
        }

        public bool ManualRender
        {
            get { return true; }
        }

        public bool Enable
        {
            get
            {
                return mHasInit;
            }
            set
            {

            }
        }

        public int RenderWidth
        {
            get { return (int)bounds.Width; }
        }

        public int RenderHeight
        {
            get { return (int)bounds.Height; }
        }

        public bool RenderTextures { get; set; }

        public bool ManualRendering
        {
            get
            {
                return false;
            }
        }

        public void RegisterTexture(Texture texture)
        {
            if (mTextures.Contains(texture))
            {
                throw new Exception("Texture already added.");
            }

            if (glControl != null && gl != null)
            {
                InternalAddTexture(texture);
            }
            else
            {
                mLoadedTextures.Add(texture);
            }
            
            mTextures.Add(texture);
        }

        public void UnregisterTexture(Texture texture)
        {
            if (!mTextures.Contains(texture))
            {
                throw new Exception("Texture can not be removed. It do not exist.");
            }

            if (glControl != null && gl != null)
            {
                InternalRemoveTexture(texture);
            }
            else
            {
                mLoadedTextures.Remove(texture);
            }
            
            mTextures.Remove(texture);
        }

        public void UpdateTexture(Texture texture)
        {

        }

        private void InternalAddTexture(Texture texture)
        {
            //if (Ready)
            //{
            //    glControl.MakeCurrent();
            //}

            gl.RegisterTexture(texture);

            //if (Ready)
            //{
            //    glControl.Context.MakeCurrent(null);
            //}
        }

        private void InternalRemoveTexture(Texture texture)
        {
            //if (Ready)
            //{
            //    glControl.MakeCurrent();
            //}

            gl.UnregisterTexture(texture);

            //if (Ready)
            //{
            //    glControl.Context.MakeCurrent(null);
            //}
        }

        public void Draw(ref SharpDepend.Datatypes.Vector4 rectangle, SharpDepend.Datatypes.Color color)
        {
            SharpDepend.Datatypes.Vector4 dummy = SharpDepend.Datatypes.Vector4.Default;
            Draw(ref rectangle, color, null, ref dummy);
        }

        public void Draw(ref SharpDepend.Datatypes.Vector4 rectangle, SharpDepend.Datatypes.Color color, Texture texture, ref SharpDepend.Datatypes.Vector4 destinationTextureUV)
        {
            if (glControl != null && gl != null)
            {
                gl.Draw(ref rectangle, color, texture, ref destinationTextureUV);
            }
        }

        public void DrawLine(SharpDepend.Datatypes.Vector2 start, SharpDepend.Datatypes.Vector2 end, SharpDepend.Datatypes.Color color)
        {
            if (glControl != null && gl != null)
            {
                gl.DrawLine(start, end, color);
            }
        }

        public void DrawCircle(SharpDepend.Datatypes.Vector2 position, double radius, SharpDepend.Datatypes.Color color)
        {
            if (glControl != null && gl != null)
            {
                gl.DrawCircle(position, radius, color);
            }
        }

        public void DrawTriangles(FastStructList<Datatypes.Vector2> vertices, FastStructList<ushort> indices, FastStructList<Datatypes.Color> color)
        {
            if (glControl != null && gl != null)
            {
                gl.DrawTriangles(vertices, indices, color);
            }
        }

        public void BeginRender()
        {
            if (LazyUpdateGL)
            {
                LazyChangeSize();
            }
        }

        
        private static volatile object SLock = new object();
        private double dpiScale;

        public void EndRender()
        {
            var tglControl = glControl;
            var tgl = gl;
            if (tglControl != null && tgl != null)
            {
                lock (SLock)
                {
                    try
                    {
                        tglControl.MakeCurrent();

                        tgl.BeginRender();

                        tgl.Update();

                        tgl.EndRender();

                        glControl.SwapBuffers();

                        tglControl.Context.MakeCurrent(null);
                    }
                    catch (Exception error)
                    {
                        // Exeption can be raised when computer goes to sleep and startup again.
                    }
                }
            }
        }
        
        public void DisposeDevice()
        {
            lock (GraphicsDisplay.Locker)
            {
                Ready = false;

                // If gl context has not been initialized.
                if (glControl != null)
                {
                    glControl.Dispose();
                    glControl = null;
                }
            }
        }
        
        public void ResetClip()
        {

        }

        public event Action ResolutionChanged;
        public event Action OnManualRender;

        public void SetClip(SharpDepend.Datatypes.Vector4 plane)
        {

        }
    }










    /// <summary>
    /// Reference: http://stackoverflow.com/questions/14246483/how-to-transfer-mouse-wheel-events-to-windowsformshost
    /// </summary>
    public static class CaptureMouseWheelWhenUnfocusedBehavior
    {
        private static readonly HashSet<WindowsFormsHost> TrackedHosts =
            new HashSet<WindowsFormsHost>();

        private static readonly System.Windows.Forms.IMessageFilter MessageFilter =
            new MouseWheelMessageFilter();

        private sealed class MouseWheelMessageFilter : System.Windows.Forms.IMessageFilter
        {
            [DllImport("User32.dll"), SuppressUnmanagedCodeSecurity]
            private static extern IntPtr WindowFromPoint(System.Drawing.Point point);

            private static System.Drawing.Point LocationFromLParam(IntPtr lParam)
            {
                //int x = (int)((((long)lParam) >> 0) & 0xffff);
                //int y = (int)((((long)lParam) >> 16) & 0xffff);
                //return new System.Drawing.Point(x, y);

                //short x2 = (short)((((long)lParam) >> 0) & 0xffff);
                //short y2 = (short)((((long)lParam) >> 16) & 0xffff);
                //return new System.Drawing.Point(x, y);

                short x = (short)lParam;
                short y = (short)((long)lParam >> 16);
                return new System.Drawing.Point(x, y);
            }

            private static bool ConsiderRedirect(WindowsFormsHost host)
            {
                var control = host.Child;
                return control != null &&
                      !control.IsDisposed &&
                       control.IsHandleCreated &&
                       control.Visible &&
                      !control.Focused;
            }

            private static int DeltaFromWParam(IntPtr wParam)
            {
                return (short)((((long)wParam) >> 16) & 0xffff);
            }

            private static System.Windows.Forms.MouseButtons MouseButtonsFromWParam(IntPtr wParam)
            {
                const int MK_LBUTTON = 0x0001;
                const int MK_MBUTTON = 0x0010;
                const int MK_RBUTTON = 0x0002;
                const int MK_XBUTTON1 = 0x0020;
                const int MK_XBUTTON2 = 0x0040;
                int buttonFlags = (int)((((long)wParam) >> 0) & 0xffff);
                var buttons = System.Windows.Forms.MouseButtons.None;
                if (buttonFlags != 0)
                {
                    if ((buttonFlags & MK_LBUTTON) == MK_LBUTTON)
                    {
                        buttons |= System.Windows.Forms.MouseButtons.Left;
                    }
                    if ((buttonFlags & MK_MBUTTON) == MK_MBUTTON)
                    {
                        buttons |= System.Windows.Forms.MouseButtons.Middle;
                    }
                    if ((buttonFlags & MK_RBUTTON) == MK_RBUTTON)
                    {
                        buttons |= System.Windows.Forms.MouseButtons.Right;
                    }
                    if ((buttonFlags & MK_XBUTTON1) == MK_XBUTTON1)
                    {
                        buttons |= System.Windows.Forms.MouseButtons.XButton1;
                    }
                    if ((buttonFlags & MK_XBUTTON2) == MK_XBUTTON2)
                    {
                        buttons |= System.Windows.Forms.MouseButtons.XButton2;
                    }
                }
                return buttons;
            }

            public bool PreFilterMessage(ref System.Windows.Forms.Message m)
            {
                const int WM_MOUSEWHEEL = 0x020A;
                if (m.Msg == WM_MOUSEWHEEL)
                {
                    var location = LocationFromLParam(m.LParam);
                    var hwnd = WindowFromPoint(location);
                    foreach (var host in TrackedHosts)
                    {
                        //if (!ConsiderRedirect(host))
                        //    continue;

                        //if (hwnd == host.Child.Handle) // Note: Not working.
                        if(host == OpenGLRenderer.MouseOverElement)
                        {
                            var delta = DeltaFromWParam(m.WParam);
                            {
                                // raise event for WPF control
                                var mouse = InputManager.Current.PrimaryMouseDevice;
                                var args = new MouseWheelEventArgs(mouse, Environment.TickCount, delta);
                                args.RoutedEvent = WindowsFormsHost.MouseWheelEvent;
                                host.RaiseEvent(args);
                            }
                            {
                                // raise event for winforms control
                                var buttons = MouseButtonsFromWParam(m.WParam);
                                var args = new System.Windows.Forms.MouseEventArgs(
                                    buttons, 0, location.X, location.Y, delta);
                                var method = typeof(System.Windows.Forms.Control).GetMethod(
                                    "OnMouseWheel",
                                    System.Reflection.BindingFlags.Instance |
                                    System.Reflection.BindingFlags.NonPublic);
                                method.Invoke(host.Child, new object[] { args });
                            }
                            return true;

                        }
                    }
                }
                return false;
            }
        }

        public static bool GetIsEnabled(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsEnabledProperty);
        }

        public static void SetIsEnabled(DependencyObject obj, bool value)
        {
            obj.SetValue(IsEnabledProperty, value);
        }

        public static readonly DependencyProperty IsEnabledProperty =
            DependencyProperty.RegisterAttached(
                "IsEnabled",
                typeof(bool),
                typeof(CaptureMouseWheelWhenUnfocusedBehavior),
                new PropertyMetadata(false, OnIsEnabledChanged));

        private static void OnIsEnabledChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var wfh = o as WindowsFormsHost;
            if (wfh == null) return;

            if ((bool)e.NewValue)
            {
                wfh.Loaded += OnHostLoaded;
                wfh.Unloaded += OnHostUnloaded;
                if (wfh.IsLoaded && TrackedHosts.Add(wfh))
                {
                    if (TrackedHosts.Count == 1)
                    {
                        System.Windows.Forms.Application.AddMessageFilter(MessageFilter);
                    }
                }
            }
            else
            {
                wfh.Loaded -= OnHostLoaded;
                wfh.Unloaded -= OnHostUnloaded;
                if (TrackedHosts.Remove(wfh))
                {
                    if (TrackedHosts.Count == 0)
                    {
                        System.Windows.Forms.Application.RemoveMessageFilter(MessageFilter);
                    }
                }
            }
        }

        private static void OnHostLoaded(object sender, EventArgs e)
        {
            var wfh = (WindowsFormsHost)sender;
            if (TrackedHosts.Add(wfh))
            {
                if (TrackedHosts.Count == 1)
                {
                    System.Windows.Forms.Application.AddMessageFilter(MessageFilter);
                }
            }
        }

        private static void OnHostUnloaded(object sender, EventArgs e)
        {
            var wfh = (WindowsFormsHost)sender;
            if (TrackedHosts.Remove(wfh))
            {
                if (TrackedHosts.Count == 0)
                {
                    System.Windows.Forms.Application.RemoveMessageFilter(MessageFilter);
                }
            }
        }
    }
}
